#python
'''Modo Skinny is a script which takes a series of image maps (which are
tied to images) and converts them to mesh weights to be used as deformations
for character animation.

Features Planned for Modo Skinny 2.0:
 - Image Symmetry:
     This will allow you to use a single value from the weight image to create
     your symmetrical wright maps. For example, the if a right vertex has a 
     weight image alpha of 0.75, the symmetrical vertex will use that same
     value, instead of looking at the weight image for its own. This
     generally should not be needed, but if the painted weight image is not
     perfectly symmetrical this can help to keep your weights symmetrical.
 - Symmetrical Weight Blending:
     This will allow you control over the center blend of a left and right 
     weight map. For example, currently modo skinny will only blend left and
     right weight maps if a vertex is exactly at X:0.0. This feature will
     allow you to smoothly blend all vertices from a user defined value, 
     such as 45cm from the center line.
'''

# Standard
import logging
import os
# Related
#import PIL.Image
import moopy.item
import moopy.mesh_element
import moopy.modo_session
import moopy.vertex_map
# Local


logger = logging.getLogger('script')

def get_image_paths(uv_map):
    '''Get a list of images to use with this script. Returned as a list
    of paths.'''

    image_maps = moopy.item.ItemCollection()
    image_maps.add_selected(item_type=moopy.item.ImageMap)

    if len(image_maps) == 0:
        raise NotImplementedError()

    image_map_paths = []

    for image_map in image_maps:
        logger.debug('Handling image map "%r"' % image_map)

        image_map_uv = image_map.uv_map

        if image_map_uv != uv_map:
            logger.info(
                'Not using image map "%s" because it is not using the '
                'selected UV Map.' % image_map_uv
            )

            image_maps.remove(image_map)
            continue

        image_map_paths.append(image_map.clip.path)

    return image_map_paths

def get_the_selected_uv_map(mesh_item):
    '''Get the uv map we are going to work with.'''
    # Get a VMap collection of the mesh item's UVs.
    uv_maps = moopy.vertex_maps.VMapCollection(mesh_item)
    uv_maps.get_selected(weight=False, morph=False, other=False)

    if len(uv_maps) == 0:
        raise NotImplementedError()

    elif len(uv_maps) > 1:
        raise NotImplementedError()

    # Get the uv_map.
    return uv_maps[0]

def get_vertex_positions(mesh_item, read_image_symmetrically):
    '''Get the posu, posv, and handle the symmetry issue, for each vertex.

    A dict is returned where the key is the vert index, and the value is
    the above information.
    '''

    if 0:
        # A little bit for WingIDE Autocomplete.
        assert isinstance(mesh_item, moopy.item.Mesh)

    # Get the selected UVs
    uv_maps = moopy.vertex_map.get_selected_uv_maps()

    # if there are more than one, raise an exception.
    if len(uv_maps) > 1:
        raise NotImplementedError()

    if len(uv_maps) == 0:
        raise NotImplementedError()

    uv_map = uv_maps[0]

    uv_vertices = uv_map.get_selected_uvs(mesh_item)

    vertices = []
    vertices_information = {}

    # This is just a count to keep track of the total vertices reading
    # from the image. Ie, not reading from their symmetrical partners.
    unique_read_count = 0

    # First we parse through the uv verts, and tag some extra information
    # onto each vertex object.
    for uv_vertex in uv_vertices:
        vertex = uv_vertex.vertex

        if vertex in vertices:
            continue

        vertex_information = {
            'posu':None,
            'posv':None,
            'read_from_symmetrical_vertex':None,
        }

        # Unpack the position
        vertex_information['posu'], vertex_information['posv'] = uv_vertex.pos

        if read_image_symmetrically:
            # Now we want to check if our symmetrical vertex has been added to
            # the vert list yet.

            # Here we get the X Symmetrical vertex, if any.
            symmetrical_vertex = vertex.symmetrical_vertex_x

            if (symmetrical_vertex is not None and
                symmetrical_vertex.selected):
                # If it has a sym vert and it is selected, we now check
                # which side of symmetry the user wants to read from.

                if (read_image_symmetrically == 'left' and
                    vertex.local_posx < 0):
                    # Because this vert is on the right side, and we want to
                    # read only from the left, we don't want to read from
                    # this vert so we give the index of the symmetrical vert.
                    vertex_information['read_from_symmetrical_vertex'] = (
                        symmetrical_vertex.index
                    )
                elif (read_image_symmetrically == 'right' and
                      vertex.local_posx > 0):
                    # Same as above, except for the left side.
                    vertex_information['read_from_symmetrical_vertex'] = (
                        symmetrical_vertex.index
                    )
                else:
                    # This vert cannot be read symmetrically, so it is a
                    # "unique read". As such, append the unique read count for
                    # debugging.
                    unique_read_count += 1
            else:
                # This vert cannot be read symmetrically, so it is a
                # "unique read". As such, append the unique read count for
                # debugging.
                unique_read_count += 1

        # Add the information we gathered into the vert list and info dict.
        vertices.append(vertex)
        vertices_information[vertex.index] = vertex_information

    # Write some debug info.
    logger.debug('Total UV Vertices: %05d' % len(uv_vertices))
    logger.debug('Total Vertices: %05d' % len(vertices))
    if read_image_symmetrically:
        logger.debug('Scheduled Unique Reads: %05d' % unique_read_count)
    else:
        logger.debug('Scheduled Unique Reads: All Reads are Unique.')

    return vertices_information

def get_vertex_weight_values(mesh_item):
    ''''''

    pass

if __name__ == '__main__':
    moopy.modo_session.initialize(
        moopy_loglvl=logging.DEBUG,
        loglvl=logging.DEBUG,
    )

    # First, setup the script options.
    script_options = moopy.modo_session.ScriptOptions()
    script_options.add_option(
        default=False, allowed_types=[bool],
        keywords=['symmetrical_weight_maps', 'sw'],
        #help='''With this option, the output of this script will create a
        #left and right weightmap. Allowing a left and a right deformer to
        #be created per image map.
        #
        #Note that any verts at the 0.0 X position will be assigned 50% control
        #of each weight map.
        #
        #In the future, this there will be a range setting, allowing you to
        #blend the maps together.'''
    )
    script_options.add_option(
        default='letter', choices=['word', 'letter'],
        keywords=['symmetry_prefix', 'sp'],
        #help='''If set to word, symmetrical prefixes on the map names will
        #be "left_" or "right_". If set to letter, map names will be
        #prefixed with "l_" or "r_".'''
    )
    script_options.add_option(
        default=False, choices=[False, 'left', 'right'],
        keywords=['read_image_symmetrically', 'si'],
        #help='''If a vertex has a selected symmetrical partner, this
        #option gives you the ability to make Modo Skinny only read from
        #one side, or both.

        #For example, if Vertex #10 is on the right side, and Vertex #20
        #is its symmetrical partner, Vertex #10 will have it's alpha looked
        #at on the image map and Vertex #20 will have Vertex #10's same
        #value applied to it. This is useful on multiple levels.

        #First off, it means that on a symmetrical mesh, if you have
        #thousands of verts selected for Modo Skinny to analyze, it 
        #only has to read image values for half of the verts on the mesh.
        #When you think about how the image map reading is by far the
        #slowest part of MS, you can often be gaining a significant speed
        #improvement.

        #Secondly sometimes modo can slightly, at the pixel level, make
        #your image maps not fully symmetrical. It's rarely noticed, but
        #when used in such a technical way a minor non-symmetrical alpha
        #difference in an image map can result in noticeably non-symmetrical
        #character deformations.'''
    )
    script_options.add_option(
        default='largest', choices=['base', 'largest'],
        keywords=['empty_handling', 'eh'],
        #help='''This option will tell MS how you want to handle any verts
        #who end up having less than a cumulative value of 1.0.

        #"base" will create a weight map named "base_weight". This will
        #simply contain all of the weights needed to make all verts reach
        #a cumulative value of 1.0.

        #"largest" will take the same value as above, and instead of putting
        #it into a new special weight map, will instead put it into the
        #weightmap that has the most control over that vertex. This is on a
        #per vertex value of course.'''
    )
    script_options.add_option(
        default=False, allowed_types=[bool],
        keywords=['create_deformers', 'cd'],
        #help='''This simply creates deformers from the weight maps after
        #they have been created. Note that it will only create a deformer if
        #a deformer there is not a deformer for that map already.'''
    )
    script_options.add_option(
        default=False, allowed_types=[bool],
        keywords=['replace_weight_maps', 'rw'],
        #help='''This is basically only good if you need to restart a large
        #group of maps. This will simply delete each weightmap that matches
        #its target name before the new map is created.
        #
        #The alternative is to not delete them, and simply assign weights
        #based on your selection, leaving existing weights outside of your
        #selection alone. This can be an effective method to piece work a
        #character, by doing its body first, then head, then arms, etc.'''
    )

    script_options.validate_options()

    # Create an item collection
    mesh_items = moopy.item.ItemCollection()

    # Add the active mesh items.
    mesh_items.add_selected(item_type=moopy.item.Mesh)

    if len(mesh_items) == 0:
        raise NotImplementedError()

    elif len(mesh_items) > 1:
        raise NotImplementedError()

    # Get the selected mesh item.
    mesh_item = mesh_items[0]

    # Get the vertex uv positions
    vertex_positions = get_vertex_positions(
        mesh_item, script_options['read_image_symmetrically'])

    # Just some dirty temporary printing.
    for verti, vertinfo in vertex_positions.items():
        print 'Vert Info: %s:%s' % (verti, vertinfo)
